use actix_web::{web, HttpRequest, HttpResponse, Result};
use validator::Validate;

use crate::{
    config::AppConfig,
    database::Database,
    error::{AppError, AppResult},
    middleware::auth::{get_user_claims, Claims},
    models::user::*,
    services::{AuthService, UserService},
    utils::validation::validate_request,
};

pub async fn register(
    req: web::Json<CreateUserRequest>,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    // Validate request
    validate_request(&req.0)?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.register(req.into_inner()).await {
        Ok(response) => Ok(HttpResponse::Created().json(response)),
        Err(e) => Err(e.into()),
    }
}

pub async fn login(
    req: web::Json<LoginRequest>,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    // Validate request
    validate_request(&req.0)?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.login(req.into_inner()).await {
        Ok(response) => Ok(HttpResponse::Ok().json(response)),
        Err(e) => Err(e.into()),
    }
}

pub async fn refresh_token(
    req: HttpRequest,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    let claims = get_user_claims(&req)
        .ok_or_else(|| AppError::unauthorized("Invalid token"))?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.refresh_token(claims.sub).await {
        Ok(response) => Ok(HttpResponse::Ok().json(response)),
        Err(e) => Err(e.into()),
    }
}

pub async fn logout(
    req: HttpRequest,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    let claims = get_user_claims(&req)
        .ok_or_else(|| AppError::unauthorized("Invalid token"))?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.logout(claims.sub).await {
        Ok(_) => Ok(HttpResponse::Ok().json(serde_json::json!({
            "message": "Logged out successfully"
        }))),
        Err(e) => Err(e.into()),
    }
}

pub async fn get_current_user(
    req: HttpRequest,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    let claims = get_user_claims(&req)
        .ok_or_else(|| AppError::unauthorized("Invalid token"))?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.get_current_user(claims.sub).await {
        Ok(user) => Ok(HttpResponse::Ok().json(user)),
        Err(e) => Err(e.into()),
    }
}

pub async fn change_password(
    req_body: web::Json<ChangePasswordRequest>,
    req: HttpRequest,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    // Validate request
    validate_request(&req_body.0)?;
    
    let claims = get_user_claims(&req)
        .ok_or_else(|| AppError::unauthorized("Invalid token"))?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.change_password(claims.sub, req_body.into_inner()).await {
        Ok(_) => Ok(HttpResponse::Ok().json(serde_json::json!({
            "message": "Password changed successfully"
        }))),
        Err(e) => Err(e.into()),
    }
}

pub async fn verify_email(
    req: HttpRequest,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    let claims = get_user_claims(&req)
        .ok_or_else(|| AppError::unauthorized("Invalid token"))?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.verify_email(claims.sub).await {
        Ok(user) => Ok(HttpResponse::Ok().json(user)),
        Err(e) => Err(e.into()),
    }
}

#[derive(serde::Deserialize, validator::Validate)]
pub struct PasswordResetRequest {
    #[validate(email(message = "Invalid email format"))]
    pub email: String,
}

pub async fn request_password_reset(
    req: web::Json<PasswordResetRequest>,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    // Validate request
    validate_request(&req.0)?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.request_password_reset(&req.email).await {
        Ok(_) => Ok(HttpResponse::Ok().json(serde_json::json!({
            "message": "Password reset email sent if account exists"
        }))),
        Err(e) => {
            // Don't reveal whether the email exists or not for security
            tracing::warn!("Password reset request failed: {}", e);
            Ok(HttpResponse::Ok().json(serde_json::json!({
                "message": "Password reset email sent if account exists"
            })))
        }
    }
}

#[derive(serde::Deserialize, validator::Validate)]
pub struct ResetPasswordRequest {
    pub token: String,
    #[validate(length(min = 8, message = "Password must be at least 8 characters long"))]
    pub new_password: String,
}

pub async fn reset_password(
    req: web::Json<ResetPasswordRequest>,
    db: web::Data<Database>,
    config: web::Data<AppConfig>,
) -> Result<HttpResponse> {
    // Validate request
    validate_request(&req.0)?;
    
    let user_service = UserService::new((**db).clone());
    let auth_service = AuthService::new(user_service, (**config).clone());
    
    match auth_service.reset_password(&req.token, &req.new_password).await {
        Ok(_) => Ok(HttpResponse::Ok().json(serde_json::json!({
            "message": "Password reset successfully"
        }))),
        Err(e) => Err(e.into()),
    }
}

pub fn configure(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::scope("/auth")
            .route("/register", web::post().to(register))
            .route("/login", web::post().to(login))
            .route("/refresh", web::post().to(refresh_token))
            .route("/logout", web::post().to(logout))
            .route("/me", web::get().to(get_current_user))
            .route("/change-password", web::post().to(change_password))
            .route("/verify-email", web::post().to(verify_email))
            .route("/request-password-reset", web::post().to(request_password_reset))
            .route("/reset-password", web::post().to(reset_password))
    );
}

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::{test, App};
    use crate::config::AppConfig;

    #[actix_web::test]
    async fn test_register_validation() {
        let config = AppConfig::default();
        let app = test::init_service(
            App::new()
                .app_data(web::Data::new(config))
                .configure(configure)
        ).await;

        let invalid_req = CreateUserRequest {
            email: "invalid-email".to_string(),
            username: "ab".to_string(), // Too short
            password: "123".to_string(), // Too short
            first_name: None,
            last_name: None,
        };

        let req = test::TestRequest::post()
            .uri("/auth/register")
            .set_json(&invalid_req)
            .to_request();
            
        let resp = test::call_service(&app, req).await;
        assert_eq!(resp.status(), 400); // Bad request due to validation errors
    }
}